@file:JvmName("CommonHelper")

package com.harvest.scientific.ladder.ext

import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Rect
import android.os.Build
import android.os.Debug
import android.text.Html
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.core.os.BuildCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import com.google.gson.reflect.TypeToken
import com.harvest.scientific.ladder.BuildConfig
import com.harvest.scientific.ladder.base.ApGlobals
import com.harvest.scientific.ladder.util.GsUtil
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.util.Locale
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

fun View.getScreenWidth() = context.resources.displayMetrics.widthPixels
fun View.getScreenHeight() = context.resources.displayMetrics.heightPixels
fun getScreenWidth() = ApGlobals.get().resources.displayMetrics.widthPixels
fun getScreenHeight() = ApGlobals.get().resources.displayMetrics.heightPixels
fun getDisplayMetrics(): DisplayMetrics = ApGlobals.get().resources.displayMetrics
fun getPackageName(): String = ApGlobals.get().packageName

fun dp2px(dp: Float, resources: Resources): Float {
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
}

fun dp2px(dp: Float): Float {
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, ApGlobals.get().resources.displayMetrics)
}
fun isDebug(action: (() -> Unit)?= null): Boolean {
    if (BuildConfig.DEBUG) {
        action?.invoke()
        return true
    }
    return false
}
fun Context.hasPermission(permission:String) = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

fun above13(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        action?.let {
            action()
        }
    }
}

fun below13(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        action?.let {
            action()
        }
    }
}

fun above10(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        action?.let {
            action()
        }
    }
}

fun below10(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        action?.let {
            action()
        }
    }
}

fun above9(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        action?.let {
            action()
        }
    }
}

fun below9(action: (() -> Unit)? = null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
        action?.let {
            action()
        }
    }
}

fun Boolean.ifTrue(action: () -> Unit) {
    if (this) {
        action()
    }
}

fun Boolean.ifFalse(action: () -> Unit) {
    if (!this) {
        action()
    }
}

val Context.asLifecycleOwner: FragmentActivity
    get() = this as FragmentActivity

val Context.asLifecycleOwnerOrNull: FragmentActivity?
    get() = this as? FragmentActivity

val View.asLifecycleOwnerOrNull: FragmentActivity?
    get() = this.context.asLifecycleOwnerOrNull

val View.asLifecycleOwner: FragmentActivity
    get() = this.context.asLifecycleOwner

val Context.scope: CoroutineScope
    get() = this.asLifecycleOwner.lifecycleScope

val Context.life: Lifecycle
    get() = this.asLifecycleOwner.lifecycle

fun Fragment.launch(action: suspend () -> Unit) {
    lifecycleScope.launch { action() }
}

fun launchOnMain(time: Long = 0L, action: suspend CoroutineScope.() -> Unit): Job {
    return launchOnCustom(CoroutineScope(Dispatchers.Main), time, action)
}

fun launchOnIO(time: Long = 0L, action: suspend CoroutineScope.() -> Unit): Job {
    return launchOnCustom(CoroutineScope(Dispatchers.IO), time, action)
}

fun launchOnCustom(scope: CoroutineScope, time: Long = 0L, action: suspend CoroutineScope.() -> Unit):Job {
    return scope.launch {
        if (time>0){//这个会丢协程线程池执行，在线程紧张的时候会new线程，如果此时线程数量也超限会崩溃，所以尽量避免这个操作
            delay(time)
        }
        action()
    }
}

fun customLaunch(action: suspend ()-> Unit) {
    CoroutineScope(Dispatchers.Main).launch {
        action()
    }
}

fun View.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
) {
    this.context.asLifecycleOwner.lifecycleScope.launch(context, start, block)
}

fun Context.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    return this.asLifecycleOwner.lifecycleScope.launch(context, start, block)
}



fun <T : View> T.visible(isVisible: Boolean = true, action: (T.() -> Unit)? = null) {
    visibility = if (isVisible) View.VISIBLE else View.GONE
    if (isVisible) {
        action?.let { this.action() }
    }
}

fun <T : View> T.visible2(isVisible: Boolean = true, action: (T.() -> Unit)? = null) {
    visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
    if (isVisible) {
        action?.let { this.action() }
    }
}
fun View.visible() {
    if (visibility != View.VISIBLE) {
        this.visibility = View.VISIBLE
    }
}

fun View.gone() {
    if (visibility != View.GONE) {
        this.visibility = View.GONE
    }
}

fun View.invisible() {
    if (visibility != View.INVISIBLE) {
        this.visibility = View.INVISIBLE
    }
}

fun visible(isVisible: Boolean, vararg views: View?) {
    views.forEach { it?.visibility = if (isVisible) View.VISIBLE else View.GONE }
}


fun View.parent() = (this.parent as? ViewGroup)

fun Any?.toJson(): String?= if (this == null) "" else GsUtil.toJson(this)

fun Any?.toBoolean() = this as? Boolean

inline fun <reified T> String.toObj() = GsUtil.fromJson(this, T::class.java)
inline fun <reified T> String.toArray() = GsUtil.fromJson<List<T>>(this, object : TypeToken<List<T>>() {}.type)
inline fun <reified K,V> String.toMap() = GsUtil.fromJson<Map<K,V>>(this, object : TypeToken<Map<K,V>>() {}.type)

fun String?.notEmpty() = !this.isNullOrEmpty()
fun CharSequence?.notEmpty() = !this.isNullOrEmpty()
fun Collection<*>?.notEmpty() = this != null && this.isNotEmpty()
fun getString(resId: Int) = ApGlobals.get().getString(resId)


val View.isInScreen: Boolean
    get() = ViewCompat.isAttachedToWindow(this) && visibility == View.VISIBLE && getLocalVisibleRect(
        Rect()
    )

fun <T> List<T>.pageIterator(pageSize: Int): Iterator<List<T>> {
    return object : Iterator<List<T>> {
        var pageIndex = 0
        override fun hasNext(): Boolean {
            return this@pageIterator.size > pageSize * pageIndex
        }

        override fun next(): List<T> {
            return this@pageIterator.subList(
                pageIndex * pageSize,
                if ((pageIndex + 1) * pageSize > this@pageIterator.size) this@pageIterator.size else (pageIndex + 1) * pageSize
            ).apply {
                pageIndex++
            }
        }
    }
}

fun String.formatToNumber(divider: Char = ','): String? {
    val number = this.toDoubleOrNull()
    number ?: return this

    val symbols = DecimalFormatSymbols().apply {
        groupingSeparator = divider
        decimalSeparator = '.'
    }

    val decimalFormat = DecimalFormat("#,###.##", symbols)
    decimalFormat.maximumFractionDigits = 2
    decimalFormat.minimumFractionDigits = 0

    // 格式化数字
    return decimalFormat.format(number)
}

fun String.toHtml() = Html.fromHtml(this)


fun countDown(
    timeMillis: Long = 1000,
    time: Int = 60,
    start: ((scope: CoroutineScope) -> Unit)? = null,
    end: (() -> Unit)? = null,
    next: ((time: Int) -> Unit)? = null,
    error: ((msg: String?) -> Unit)? = null
): Job {
    return GlobalScope.launch(Dispatchers.Main) {
        flow {
            (time downTo 0).forEach {
                emit(it)
                delay(timeMillis)
            }
        }.onStart {
            start?.invoke(this@launch)
        }.onCompletion {
            end?.invoke()
        }.catch {
            error?.invoke(it.message)
        }.collect {
            next?.invoke(it)
        }
    }
}